package com.web_bloks.types.client;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import com.google.gwt.core.client.GWT;

/**
 * Generic Property class
 * 
 * The design objective was not to define a class
 * of this nature for handling efficiently a 
 * large number of instances. If a large number of
 * properties if required, it is suggested that one
 * encapsulates a <code>value object</code> of the
 * proper type/size inside a generic property.   
 * 
 * @author Jean-Lou Dupont
 * TODO check if optimization is adequately performed by GWT
 * 
 */
public class Property implements IProperty {

	String key = null;
	Object value = null;
	
	boolean dirty = false;
	boolean lock  = false;
	
	/**
	 * Lazy init
	 */
	HashMap<EPropertyMeta, Boolean> metas = new HashMap<EPropertyMeta, Boolean>();
	
	/**
	 * Helper for the Factory
	 */
	protected Property() {
		super();
	}
	
	protected Property(String key) {
		super();
		this.key = key;
	}
	
	/**
	 * Use with care!
	 * @param key
	 */
	protected void setKey(String key) {
		this.key = key;
	}
	
	/**
	 * Sets meta information
	 * 
	 * @param meta
	 * @throws IPropertyException
	 */
	public void setMeta(EPropertyMeta meta) throws IPropertyException {
		
		if (getLock())
			throw new IPropertyException(this.key, IPropertyException.Code.LOCKED);

		if (hasMeta(EPropertyMeta.READ_ONLY))
			throw new IPropertyException(this.key, IPropertyException.Code.READ_ONLY);

		metas.put( meta, true );
		this.dirty = true;
	}
	
	/**
	 * Used at creation time.
	 * The client is responsible for managing
	 * the <code>dirty</code> status.
	 * 
	 * @param meta
	 */
	protected void safeSetMeta(EPropertyMeta meta) {
		metas.put( meta, true );
	}
	
	/**
	 * Use for managing updates / persistence etc.
	 */
	public boolean isDirty() {
		return this.dirty;
	}
	
	/**
	 * Use with caution
	 */
	public void clearDirty() {
		this.dirty = false;
	}
	
	/**
	 * Use with caution
	 */
	public void lock() {
		this.lock = true;
	}
	
	/**
	 * Use with caution
	 */
	public void unlock() {
		this.lock = false;
	}
	
	/**
	 * Mainly for debugging purposes
	 * 
	 * @return boolean
	 */
	public boolean getLock() {
		return lock;
	}
	/**
	 * Creates a new property with key 'key'
	 * 
	 * @param key
	 * @return property instance
	 * @throws IPropertyException
	 */
	public static Property create(String key) throws IPropertyException {

		if ( null == key || key.length()==0 ) {
			throw new IPropertyException(key, IPropertyException.Code.KEY_NULL);
		}
		
		Property P = (Property) GWT.create( Property.class );
		
		P.setKey(key);
		P.lock();
		
		return P;
	}//
	
	/**
	 * Creates a new property with key 'key'.
	 * This is a 'safe' version i.e. the client
	 * is responsible for handling what could be
	 * errors accessing this method.
	 * 
	 * @param key
	 * @return property instance
	 */
	public static Property safeCreate(String key, Object value) {

		Property P = (Property) GWT.create( Property.class );
		
		P.setKey(key);
		P.safeSetValue(value);
		P.lock();
		
		return P;
	}//
	
	/**
	 * Creates a new property with key 'key'.
	 * This is a 'safe' version i.e. the client
	 * is responsible for handling what could be
	 * errors accessing this method.
	 * 
	 * @param key
	 * @param ms meta list
	 * @return property instance
	 */
	public static Property safeCreate(String key, EPropertyMeta[] ms) {

		Property P = (Property) GWT.create( Property.class );
		
		P.setKey(key);
		
		if (null!=ms) {
			for(int i=0;i<ms.length;i++) {
				P.safeSetMeta( ms[i] );
			}
		}
		P.lock();
		
		return P;
	}//

	/**
	 * Creates a new property with key 'key'.
	 * This is a 'safe' version i.e. the client
	 * is responsible for handling what could be
	 * errors accessing this method.
	 * 
	 * @param key
	 * @param ms meta list
	 * @return property instance
	 */
	public static Property safeCreate(String key, Object value, EPropertyMeta[] ms) {

		Property P = (Property) GWT.create( Property.class );
		
		P.setKey(key);
		P.safeSetValue(value);
		
		if (null!=ms) {
			for(int i=0;i<ms.length;i++) {
				P.safeSetMeta( ms[i] );
			}
		}
		P.lock();
		
		return P;
	}//

	/**
	 * Get key
	 * @return key
	 */
	public String getKey() {
		return this.key;
	}
	
	/**
	 * Set value
	 * 
	 * @param value
	 * @throws IPropertyException
	 */
	public void setValue(Object value) throws IPropertyException {
		
		if (getLock())
			throw new IPropertyException(this.key, IPropertyException.Code.LOCKED);
		
		if (hasMeta(EPropertyMeta.READ_ONLY))
			throw new IPropertyException(this.key, IPropertyException.Code.READ_ONLY);
		
		this.value = value;
		this.dirty = true;
	}
	/**
	 * Used at creating time (mainly).
	 * The client is responsible for managing
	 * the <code>dirty</code> status.
	 * 
	 * @param value
	 */
	protected void safeSetValue(Object value) {
		this.value = value;
	}
	/**
	 * Get value
	 * @return
	 */
	public Object getValue() {
		return this.value;
	}

	@SuppressWarnings("unchecked")
	public Class getValueType() {
		
		if (null==value)
			return null;
		
		return value.getClass();
	}

	/**
	 * Verifies the presence of a specified meta field
	 */
	public boolean hasMeta(EPropertyMeta meta) throws IPropertyException {
		
		if (metas.isEmpty())
			return false;

		return metas.containsKey(meta);
	}

	/**
	 * GETTER for meta information
	 * 
	 * TODO optimize with caching taking into account <code>dirty</code> status
	 * @return metas
	 *  
	 */
	public EPropertyMeta[] getMeta() {
		
		Set<EPropertyMeta> keys = metas.keySet();
		Iterator<EPropertyMeta> iter = keys.iterator();
		
		int sz = keys.size();
		if (sz == 0)
			return null;
		
		EPropertyMeta[] all = new EPropertyMeta[ sz ]; 
		
		int i = 0;
		while(iter.hasNext()) {
			all[i++] = iter.next();
		}
		
		return all;
	}

	
}//END